package com.sailing.xjpb.common.tree;

import com.sailing.xjpb.common.SailingReflectionUtils;
import com.sailing.xjpb.common.exception.IpaasTreeException;
import com.sailing.xjpb.common.tree.annotation.Tree;
import com.sailing.xjpb.common.tree.annotation.TreeField;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 将标有{@linkplain Tree}和继承了接口{@linkplain SailingTree}的对象转换为树形结构
 *
 * @author YW
 */
public final class SailingTreeUtils {

    private SailingTreeUtils() {

    }

    /**
     * 缓存对象的字段信息
     */
    private static final Map<Class<?>, TreeFieldInfo> FIELD_CACHE = new ConcurrentHashMap<>(256);

    /**
     * 解析所有的节点信息，形成具有父子关系的树
     *
     * @param treeNodes 树节点
     */
    public static <T extends SailingTree<T>> List<T> parseTree(List<T> treeNodes) {
        List<T> rootNodes = treeNodes.parallelStream().filter(e -> isRootNode(e, treeNodes))
                .collect(Collectors.toList());
        rootNodes.forEach(e -> setChildren(e, treeNodes));
        return rootNodes;
    }

    /**
     * 将任意标注为{@linkplain Tree}的对象数据转换为ScppVueTree结构的数据
     *
     * @param originalDataList 标注为{@linkplain Tree}的类的对象
     */
    public static List<SailingVueTree> getVueTree(List<?> originalDataList) {
        // 先将数据转换为树形结构的节点（不包含父子关系）
        Pair<List<SailingVueTree>, Boolean> treeNodeInfos = parseTreeNodes(originalDataList);
        // List-节点信息和是Boolean-否需要解析子节点，不需要解析子节点直接返回list信息
        return treeNodeInfos.getRight() ? getTree(treeNodeInfos.getLeft()) : treeNodeInfos.getLeft();
    }

    /**
     * 将任意标注为{@linkplain Tree}的对象数据转换为ScppVueTree结构的数据
     *
     * @param originalDataList 标注为{@linkplain Tree}的类的对象
     * @param isLeaf           是否是叶子节点
     */
    public static List<SailingVueTree> getTreeNodes(List<?> originalDataList, boolean isLeaf) {
        Pair<List<SailingVueTree>, Boolean> treeNodeInfos = parseTreeNodes(originalDataList);
        List<SailingVueTree> treeNodes = treeNodeInfos.getLeft();
        if (isLeaf) {
            for (SailingVueTree scppVueTree : treeNodes) {
                scppVueTree.setLeaf(true);
            }
        }
        return treeNodes;
    }

    /**
     * 获取当前节点以及所有子节点
     *
     * @param treeNodes 树节点
     */
    public static <T extends SailingTree<T>> List<T> listAllNodes(List<T> treeNodes) {
        List<T> allNodes = new ArrayList<>();
        for (T currentNode : treeNodes) {
            listAllNodes(currentNode, allNodes);
        }
        return allNodes;
    }

    /**
     * 获取当前节点以及所有子节点
     *
     * @param currentNode 当前树节点
     */
    public static <T extends SailingTree<T>> List<T> listAllNodes(T currentNode) {
        List<T> allNodes = new ArrayList<>();
        listAllNodes(currentNode, allNodes);
        return allNodes;
    }

    /**
     * 解析所有的节点信息，形成具有父子关系的树
     *
     * @param treeNodes 树节点信息
     */
    public static List<SailingVueTree> getTree(List<SailingVueTree> treeNodes) {
        List<SailingVueTree> rootNodes = treeNodes.parallelStream().filter(e -> isRootNode(e, treeNodes))
                .collect(Collectors.toList());

        rootNodes.forEach(e -> setChildren(e, treeNodes));
        return rootNodes;
    }

    /**
     * 先将数据转换为树形结构的节点（不包含父子关系）
     *
     * @param originalDataList 标注有注解{@linkplain Tree}的对象
     * @return Pair<List < ScppVueTree>,
     * Boolean>：List-节点信息和是boolean-否需要解析子节点，不需要解析子节点直接返回list信息
     */
    private static Pair<List<SailingVueTree>, Boolean> parseTreeNodes(List<?> originalDataList) {
        if (CollectionUtils.isEmpty(originalDataList)) {
            return Pair.of(new ArrayList<>(), false);
        }
        Class<?> dataClass = SailingReflectionUtils.getItemClass(originalDataList);
        // 先将数据转换为树形结构的节点（不包含父子关系）
        return parseTreeNodes(dataClass, originalDataList);
    }

    /**
     * 先将数据转换为树形结构的节点（不包含父子关系）
     *
     * @param dataClass        标注有注解{@linkplain Tree}的类
     * @param originalDataList 标注有注解{@linkplain Tree}的对象
     * @return Pair<List < ScppVueTree>,
     * Boolean>：List-节点信息和是boolean-否需要解析子节点，不需要解析子节点直接返回list信息
     */
    private static Pair<List<SailingVueTree>, Boolean> parseTreeNodes(Class<?> dataClass, List<?> originalDataList) {
        TreeFieldInfo treeFieldInfo = getTreeField(dataClass);
        try {
            MutablePair<List<SailingVueTree>, Boolean> rsPair = new MutablePair<>();
            List<SailingVueTree> treeNodes = new ArrayList<>();
            for (Object data : originalDataList) {
                SailingVueTree scppVueTree = getVueTree(treeFieldInfo, data);
                treeNodes.add(scppVueTree);
            }
            // 设置节点信息
            rsPair.setLeft(treeNodes);
            // 设置是否需要子节点
            rsPair.setRight(treeFieldInfo.getParentIdField() != null);
            return rsPair;
        } catch (Exception e) {
            throw new IpaasTreeException("将树形结构出错", e);
        }
    }

    /**
     * 根据配置信息转换为树节点信息
     *
     * @param treeFieldInfo 配置信息
     * @param data          待转换的对象
     * @throws IllegalAccessException 字段不匹配异常
     */
    private static SailingVueTree getVueTree(TreeFieldInfo treeFieldInfo, Object data) throws IllegalAccessException {
        // 设置ID等关键信息
        SailingVueTree scppVueTree = new SailingVueTree();
        String treeId = Objects.toString(treeFieldInfo.getTreeIdField().get(data), null);
        scppVueTree.setTreeId(treeId);
        String treeName = Objects.toString(treeFieldInfo.getTreeNameField().get(data), null);
        scppVueTree.setTreeName(treeName);
        String treeCode = Objects.toString(treeFieldInfo.getTreeCodeField().get(data), null);
        scppVueTree.setTreeCode(treeCode);
        if (treeFieldInfo.getParentIdField() != null) {
            // 设置父ID信息
            String parentId = Objects.toString(treeFieldInfo.getParentIdField().get(data), null);
            scppVueTree.setParentId(parentId);
        }
        // 设置属性信息
        Set<Entry<Field, TreeField>> propertiesFields = treeFieldInfo.getPropertiesFields().entrySet();
        for (Entry<Field, TreeField> propertiesField : propertiesFields) {
            Field field = propertiesField.getKey();
            TreeField treeField = propertiesField.getValue();
            String propName = StringUtils.isEmpty(treeField.propName()) ? field.getName() : treeField.propName();
            Object propValue = field.get(data);
            String defaultValue = treeField.defaultValue();
            if (propValue == null && !"".equals(defaultValue)) {
                propValue = defaultValue;
            }
            scppVueTree.addProperty(propName, propValue);
        }
        return scppVueTree;
    }

    /**
     * 设置当前节点的子节点信息
     *
     * @param currentNode 当前节点
     * @param treeNodes   所有节点
     */
    private static void setChildren(SailingVueTree currentNode, List<SailingVueTree> treeNodes) {
        String id = currentNode.getTreeId();
        List<SailingVueTree> children = treeNodes.parallelStream().filter(e -> StringUtils.equals(e.getParentId(), id))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(children)) {
            currentNode.setLeaf(true);
            return;
        }
        currentNode.getChildren().addAll(children);
        children.parallelStream().forEach(child -> {
            child.setParentId(id);
            setChildren(child, treeNodes);
        });
    }

    /**
     * 设置当前节点的子节点信息
     *
     * @param currentNode 当前节点
     * @param treeNodes   所有节点
     */
    private static <T extends SailingTree<T>> void setChildren(T currentNode, List<T> treeNodes) {
        String id = currentNode.getTreeId();
        List<T> children = treeNodes.parallelStream().filter(e -> StringUtils.equals(e.getParentId(), id))
                .collect(Collectors.toList());
        if (children.isEmpty()) {
            currentNode.setChildren(new ArrayList<>());
        } else {
            currentNode.setChildren(children);
        }
        for (T child : children) {
            setChildren(child, treeNodes);
        }
    }

    /**
     * 判断是否是根节点
     *
     * @param currentNode 当前节点
     * @param treeNodes   所有节点信息
     */
    private static <T extends SailingTree<?>> boolean isRootNode(T currentNode, List<T> treeNodes) {
        String pid = currentNode.getParentId();
        return treeNodes.parallelStream().noneMatch(e -> StringUtils.equals(e.getTreeId(), pid));
    }

    /**
     * 获取当前节点以及所有子节点
     *
     * @param currentRoot 当前节点
     * @param allNodes    当前节点以及所有子节点信息
     */
    private static <T extends SailingTree<T>> void listAllNodes(T currentRoot, List<T> allNodes) {
        allNodes.add(currentRoot);
        List<T> children = currentRoot.getChildren();
        if (CollectionUtils.isEmpty(children)) {
            return;
        }
        // 子节点信息
        for (T child : children) {
            listAllNodes(child, allNodes);
        }
    }

    /**
     * 解析类上的Tree注解,根据注解信息获取Tree注解上标注的字段对应信息
     *
     * @param dataClass 标注有@Tree的类
     */
    private static TreeFieldInfo getTreeField(Class<?> dataClass) {
        TreeFieldInfo treeFieldInfo = FIELD_CACHE.get(dataClass);
        if (treeFieldInfo != null) {
            return treeFieldInfo;
        }

        Tree treeAnnotation = dataClass.getAnnotation(Tree.class);
        if (treeAnnotation == null) {
            throw new IpaasTreeException(dataClass.getName() + "-未配置@Tree注解，无法生成树形结构！");
        }
        treeFieldInfo = getTreeFieldInfo(dataClass, treeAnnotation);
        FIELD_CACHE.put(dataClass, treeFieldInfo);
        return treeFieldInfo;
    }

    /**
     * 根据类和注解信息获取Tree注解上标注的字段对应信息
     *
     * @param dataClass      标注有@Tree的类
     * @param treeAnnotation Tree注解信息
     */
    private static TreeFieldInfo getTreeFieldInfo(Class<?> dataClass, Tree treeAnnotation) {

        TreeFieldInfo treeFieldInfo = new TreeFieldInfo();
        String idFieldName = treeAnnotation.id();
        String pidFieldName = treeAnnotation.pid();
        String labelFieldName = treeAnnotation.label();
        String codeFieldName = treeAnnotation.code();

        Field[] fields = dataClass.getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            boolean setAccessible = false;
            if (StringUtils.equals(idFieldName, fieldName)) {
                setAccessible = true;
                treeFieldInfo.setTreeIdField(field);
            }

            if (StringUtils.equals(pidFieldName, fieldName)) {
                setAccessible = true;
                treeFieldInfo.setParentIdField(field);
            }

            if (StringUtils.equals(labelFieldName, fieldName)) {
                setAccessible = true;
                treeFieldInfo.setTreeNameField(field);
            }

            if (StringUtils.equals(codeFieldName, fieldName)) {
                setAccessible = true;
                treeFieldInfo.setTreeCodeField(field);
            }

            TreeField treeField = field.getAnnotation(TreeField.class);
            if (treeField != null) {
                setAccessible = true;
                treeFieldInfo.addPropertiesField(field, treeField);
            }

            if (setAccessible) {
                field.setAccessible(true);
            }
        }

        if (treeFieldInfo.getTreeIdField() == null || treeFieldInfo.getTreeNameField() == null
                || treeFieldInfo.getTreeCodeField() == null) {
            throw new IpaasTreeException(dataClass.getName() + "中未找到Field[" + idFieldName + "|" + labelFieldName + "|"
                    + codeFieldName + " ]");
        }

        return treeFieldInfo;
    }
}
